package cn.rkylin.oms.common.event.message.handler;

import cn.rkylin.oms.common.event.message.*;
import cn.rkylin.oms.system.user.dao.IUserDAO;
import cn.rkylin.oms.system.user.domain.UserContact;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.MailException;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.retry.RecoveryCallback;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryPolicy;
import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;

import java.util.*;

import static cn.rkylin.oms.common.event.message.MessageTemplateVariable.RECEIVER;
import static cn.rkylin.oms.common.event.message.ReceiverType.USER;
import static cn.rkylin.oms.common.event.message.SendResult.FAILED;
import static cn.rkylin.oms.common.event.message.SendResult.SUCCESS;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.transform;

public class EmailMessageHandler extends MessageHandler {

	private static final Logger logger = Logger.getLogger(EmailMessageHandler.class);

	@Autowired
	private MailSender mailSender;

	@Autowired
	private IUserDAO userDAO;

	@Override
	protected void internalHandle(MessageEvent event) {
		List<UserContact> userContacts = findUserContact(event);
		for (UserContact userContact : userContacts) {
			// send the email to user one by one, because we just want the user know his/her own addresses from <TO> field.
			if (!isNullOrEmpty(userContact.getEmail())) {
				sendEvent(userContact, event);
			}
		}
	}

	@Override
	protected SendWay serveSendWay() {
		return SendWay.EMAIL;
	}

	private List<UserContact> findUserContact(MessageEvent event) {
		try {
			return userDAO.retrieveUserContacts(filterReceiverIds(event, USER));
		} catch (Exception e) {
			logger.error("can't retrieve the users' email addresses, so can't sent the email to them.", e);
		}

		return new ArrayList<UserContact>();
	}

	private List<String> filterReceiverIds(MessageEvent event, final ReceiverType receiverType) {
		Iterable<String> filteredReceiverIds = transform(
				filter(event.getReceivers(), new Predicate<MessageReceiver>() {
					@Override
					public boolean apply(MessageReceiver receiver) {
						return receiver.getReceiverType() == receiverType;
					}
				}),
				new Function<MessageReceiver, String>() {
					@Override
					public String apply(MessageReceiver receiver) {
						return receiver.getReceiverId();
					}
				}
		);
		return Lists.newArrayList(filteredReceiverIds);
	}

	private void sendEvent(final UserContact userContact, final MessageEvent event) throws MailException {
		final String content = getContent(userContact, event);
		RetryTemplate template = new RetryTemplate();
		template.setRetryPolicy(getRetryPolicy(event));
		template.setBackOffPolicy(getBackOffPolicy(event));
		template.execute(
				new RetryCallback<RetryContext, MailException>() {
					@Override
					public RetryContext doWithRetry(RetryContext retryContext) throws MailException {
						// this is real action to send the email
						sendEventInternal(userContact, event, content);
						return retryContext;
					}
				},
				new RecoveryCallback<RetryContext>() {
					@Override
					public RetryContext recover(RetryContext retryContext) throws Exception {
						logger.warn("send mail failed.", retryContext.getLastThrowable());
						writeLog(event.getCreationTime(), new Date(), content, FAILED);
						return retryContext;
					}
				}
		);
	}

	private void sendEventInternal(UserContact userContact, MessageEvent event, String content) {
		Date sendTime = new Date();
		MessageDefinition definition = event.getDefinition();
		SimpleMailMessage mailMessage = new SimpleMailMessage();
		// TODO should find another way to retrieve the user address for sender.
		mailMessage.setFrom(((JavaMailSenderImpl) mailSender).getUsername());
		mailMessage.setTo(userContact.getEmail());
		mailMessage.setSubject(definition.getTitle());
		mailMessage.setText(content);
		mailMessage.setSentDate(sendTime);
		mailSender.send(mailMessage);
		writeLog(event.getCreationTime(), sendTime, content, SUCCESS);
	}

	private String getContent(UserContact userContact, MessageEvent event) {
		MessageDefinition definition = event.getDefinition();
		EnumMap<MessageTemplateVariable, Object> variables = Maps.newEnumMap(event.getVariables());
		variables.put(RECEIVER, userContact.getName());
		return definition.fillVariables(variables);
	}

	private RetryPolicy getRetryPolicy(MessageEvent event) {
		Map<Class<? extends Throwable>, Boolean> exceptionTypeMap = new HashMap<Class<? extends Throwable>, Boolean>();
		exceptionTypeMap.put(MailException.class, true);
		return new SimpleRetryPolicy(event.getDefinition().getRetryCount(), exceptionTypeMap);
	}

	private BackOffPolicy getBackOffPolicy(MessageEvent event) {
		FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
		backOffPolicy.setBackOffPeriod(event.getDefinition().getRetryInterval() * 1000);
		return backOffPolicy;
	}

}